How to take partial screenshot with Selenium WebDriver in python?
Asked Answered
S

8

92

I have searched a lot for this but couldn't find a solution. Here's a similar question with a possible solution in java.

Is there a similar solution in Python?

Siddons answered 22/2, 2013 at 6:41 Comment(0)
E
164

Other than Selenium, this example also requires the PIL Imaging library. Sometimes this is put in as one of the standard libraries and sometimes it's not, but if you don't have it you can install it with pip install Pillow

from selenium import webdriver
from PIL import Image
from io import BytesIO

fox = webdriver.Firefox()
fox.get('http://stackoverflow.com/')

# now that we have the preliminary stuff out of the way time to get that image :D
element = fox.find_element_by_id('hlogo') # find part of the page you want image of
location = element.location
size = element.size
png = fox.get_screenshot_as_png() # saves screenshot of entire page
fox.quit()

im = Image.open(BytesIO(png)) # uses PIL library to open image in memory

left = location['x']
top = location['y']
right = location['x'] + size['width']
bottom = location['y'] + size['height']


im = im.crop((left, top, right, bottom)) # defines crop points
im.save('screenshot.png') # saves new cropped image

and finally the output is... the Stackoverflow logo!!!

enter image description here

Now of course this would be overkill for just grabbing a static image but if your want to grab something that requires Javascript to get to this could be a viable solution.

Emie answered 8/4, 2013 at 3:16 Comment(16)
You can also get the screenshot in memory directly: img = Image.open(StringIO(base64.decodestring(driver.get_screenshot_as_base64())))Yahrzeit
I confirm that this solution (along with @ejk314's suggestion) works very well with Selenium 2.38.4, using the Firefox driverCricket
An alternative for in memory loading is img = fox.get_screenshot_as_png() and then img = Image.open(StringIO(img)) to load it as PIL image.Astrogation
comment taken from a NAA of @sukrit-gupta : RandomPhobia, your answer works great in case of static pages which do not need to be scrolled. In case, someone needs to get images from large pages which need scrolling you should use the location_once_scrolled_into_view function. So replace, location = element.location with: location = img.location_once_scrolled_into_view Also, make sure that you use Chrome instead of Firefox because Chrome takes screenshot of the visible area only whereas Firefox takes screenshot of the complete tab.Griffy
how we capure the which show in browser without scroll a page not how whole page.Superbomb
For some reason im.save('screenshot.png') is not saving.Bartz
@Bartz "some reason is not enough" U should wrap with a try/except and print str(e) from Exception every time you don't know what to expect. Maybe you didn't load or process the image correctly, so save will not work, but anyway, using try block you will get the error and will be able to solve it.Ionian
Followup on the suggestion by @yellowcap: Note in Python 3+, you should BytesIO rather than StringIO.Jarl
I'm facing resize problem, while image is zommed out. Screenshot is working but cropping the image doesn't seem to work. Did anyone face the same ?Koloski
On MacOS (retina) there is problem that web element position in pixels dont match element position in screenshot, due to resize/ratioBlooded
As mentioned by @Blooded sometimes due to the pixel density of screens, some corrections should be done. Because element.size is in points and crop function use pixels we should use pixel ratio to transform sizes. pixel_ratio = fox.execute_script("return window.devicePixelRatio") new_size = old_size * pixel_ratioWisp
The size of the Screenshot and the browser window size are the same. I had to do the following 1. get the browser window size using driver.get_window_size() 2. Subtract the height of the address bar from the height of the browser window 3. Now resize the screenshot to the new height and width from set 1 and 2Polyhymnia
@Wisp actually since old_size is dict and pixel_ratio is int, one needs to recalculate height and width: right = location['x'] + old_size['width']* pixel_ratio bottom = location['y'] + old_size['height']* pixel_ratioRevivalist
As mentioned by @Bartz as well, the im.save is not saving but it is also not throwing error, should I ask a new question for this? When I type im.show() a transparent image window of required size is appearingNucleonics
Hi, Thank you for clear explanation. I tried to capture screenshot from CAPTCHA, but this code screens wrong and unrelated pixels were captured. I use xpath for finding place of CAPTCHA.Criticism
Get: AttributeError: type object 'ElementObject' has no attribute 'location'Zygote
Y
52

Worked for me in python3.5

from selenium import webdriver


fox = webdriver.Firefox()
fox.get('http://stackoverflow.com/')
image = fox.find_element_by_id('hlogo').screenshot_as_png

p.s.

To save to file

image=driver.find_element_by_id('hlogo').screenshot(output_file_path)
Yarn answered 24/12, 2017 at 10:13 Comment(9)
@Julius no, it should not be - it is enough only when partial screenshot exactly matched element with element that has assigned id.Cruelty
@Julius it's still uncertain where and how it works. In Python 2.7 with Chrome, it does not work.Bartz
worked on chrome/python 2.7 for me with selenium2. image = driver.find_element_by_id('el_id').screenshot_as_png is this an attribute of the element, and how can i save this as an image?Herrod
how do we proceed to save the image?Postmistress
@Herrod @Lakshmi You guys can do image = driver.find_element_by_id('el_id').screenshot(output_file_path). Please refer here for API doc.Incommunicado
I have to do 'image.open' before saving as suggested above by Gavriel Cohen. im = Image.open(BytesIO(image)) # uses PIL library to open image in memory im.save('example.png')Laevo
there is also a screenshot_as_base64 attributePigtail
This should be the accaped answer for me.Dvinsk
excellent. solved my problem in only 3 lines. Way better than dealing with crop regions. In my case worked with python3.8 and selenium 3.1. thkssSalvage
S
9

I wrote this useful python3 function.

from base64 import b64decode
from wand.image import Image
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.action_chains import ActionChains
import math

def get_element_screenshot(element: WebElement) -> bytes:
    driver = element._parent
    ActionChains(driver).move_to_element(element).perform()  # focus
    src_base64 = driver.get_screenshot_as_base64()
    scr_png = b64decode(src_base64)
    scr_img = Image(blob=scr_png)

    x = element.location["x"]
    y = element.location["y"]
    w = element.size["width"]
    h = element.size["height"]
    scr_img.crop(
        left=math.floor(x),
        top=math.floor(y),
        width=math.ceil(w),
        height=math.ceil(h),
    )
    return scr_img.make_blob()

It returns png image of displayed element as bytes. Limitation: element must fit in viewport.
You must install wand module to work with it.

Staten answered 20/6, 2016 at 14:17 Comment(1)
Nice code! When I trying with long page in chrome, I think x = element.location_once_scrolled_into_view["x"] y = element.location_once_scrolled_into_view["y"] since location may return a y larger than the window.Separates
P
7

Here is a function that does just that, The sizes must be casted to integers before being passed to the crop function :

from PIL import Image
from StringIO import StringIO
def capture_element(element,driver):
  location = element.location
  size = element.size
  img = driver.get_screenshot_as_png()
  img = Image.open(StringIO(img))
  left = location['x']
  top = location['y']
  right = location['x'] + size['width']
  bottom = location['y'] + size['height']
  img = img.crop((int(left), int(top), int(right), int(bottom)))
  img.save('screenshot.png')
Phanotron answered 21/6, 2017 at 21:38 Comment(5)
This is pretty much the same as the accepted answers, with the added fault that it does not uses Selenium, as the OP wanted.Gittel
It does use selenium, but the import statement is not needed here, It also includes the conversion of locations from float to int, the function img.crop throws an exception if the locations are not integersPhanotron
TypeError: initial_value must be str or None, not bytes Vow
what is your print(img), is a method or Byte object?Vow
i am not sure i understand your questionPhanotron
W
5

Expanding on the comments in response to RandomPhobia's very nice answer, here are two solutions with correct import statements that will open a full-screen screenshot without first saving to a file:

from selenium import webdriver
from PIL import Image
from StringIO import StringIO
import base64

DRIVER = 'chromedriver'
browser = webdriver.Chrome(DRIVER)

browser.get( "http:\\\\www.bbc.co.uk" )

img 1 = Image.open(StringIO(base64.decodestring(browser.get_screenshot_as_base64())))

img 2 = Image.open(StringIO(browser.get_screenshot_as_png()))

And because I'm sure your next question is, "Well that's great but which one is fastest?", here's how to determine it (I find the first method to be the fastest by some distance):

import timeit

setup = '''
from selenium import webdriver
from PIL import Image
from StringIO import StringIO
import base64

DRIVER = 'chromedriver'
browser = webdriver.Chrome(DRIVER)
browser.get( "http:\\\\www.bbc.co.uk" )

file_name = 'tmp.png'
'''

print timeit.Timer('Image.open(StringIO(browser.get_screenshot_as_png()))', setup=setup).repeat(2, 10)
print timeit.Timer('Image.open(StringIO(base64.decodestring(browser.get_screenshot_as_base64())))', setup=setup).repeat(2, 10)
print timeit.Timer('browser.get_screenshot_as_file(file_name); pil_img = Image.open(file_name)', setup=setup).repeat(2, 10)
Welsh answered 28/1, 2018 at 20:13 Comment(0)
Z
5

Screenshot by Element:

from PIL import Image
from io import BytesIO


image = self.browser.driver.find_element_by_class_name('example.bla.bla').screenshot_as_png
im = Image.open(BytesIO(image))  # uses PIL library to open image in memory
im.save('example.png')
Zygote answered 25/11, 2019 at 8:47 Comment(2)
I do the same but I face with the following error: WebDriverException: Message: unknown error: failed to parse value of getElementRegion (Session info: chrome=78.0.3904.108)Braxy
@MostafaGhadimi pls check this: sqa.stackexchange.com/questions/40321/…Zygote
G
4

Just simple as that:

element = driver.find_element_by_class_name('myclass')
element.screenshot('screenshot.png')
Glosseme answered 27/2, 2021 at 19:16 Comment(1)
At least in Selenium 4 the filename needs to be a full pathDairy
C
2

I converted @randomphobia's answer into a function. I also used @bummis' suggestion of using location_once_scrolled_into_view instead of location in order to generalize no matter the size of the page.

from selenium import webdriver
from PIL import Image
from io import BytesIO

def take_screenshot(element, driver, filename='screenshot.png'):
  location = element.location_once_scrolled_into_view
  size = element.size
  png = driver.get_screenshot_as_png() # saves screenshot of entire page

  im = Image.open(BytesIO(png)) # uses PIL library to open image in memory

  left = location['x']
  top = location['y']
  right = location['x'] + size['width']
  bottom = location['y'] + size['height']


  im = im.crop((left, top, right, bottom)) # defines crop points
  im.save(filename) # saves new cropped image

Here's a gist: https://gist.github.com/WittmannF/b714d3ceb7b6a5cd50002f11fb5a4929

Carbonization answered 10/1, 2020 at 21:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.